package com.gec.system.filter;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gec.model.entity.SysUser;
import com.gec.model.vo.LoginVo;
import com.gec.system.service.SysLoginLogService;
import com.gec.system.util.*;
import com.gec.system.custom.CustomUser;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 登录过滤器，继承UsernamePasswordAuthenticationFilter，对用户名密码进行登录校验
 * </p>
 *
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {


    private RedisTemplate redisTemplate;

    private SysLoginLogService sysLoginLogService;

    //    1、改造 请求路径 请求方式
    public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate, SysLoginLogService sysLoginLogService) {

        this.setAuthenticationManager(authenticationManager);
        this.setPostOnly(false);
        //指定登录接口及提交方式，可以指定任意路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login", "POST"));

        this.redisTemplate=redisTemplate;

        this.sysLoginLogService=sysLoginLogService;

    }

    //    2、重写认证方法逻辑
//    接收前端的 username  password  进行认证
//    authenticate  一旦调用该方法就一定去调用UserDetailService 去查询数据库
    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

//        loginVo 对象
        ObjectMapper objectMapper = new ObjectMapper();
//public <T> T readValue(InputStream src, Class<T> valueType)
        LoginVo loginVo=objectMapper.readValue(request.getInputStream(),LoginVo.class);

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());

//        this.setDetails(request,authRequest);

        return this.getAuthenticationManager().authenticate(authenticationToken);
    }


    //    3、认证成功的逻辑
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//      1、从Authentication 取出 SysUser
        CustomUser customerUser =(CustomUser)authResult.getPrincipal();
        SysUser sysUser = customerUser.getSysUser();

//        2、通过jwt 颁发 token 令牌给到前端
        String token= JwtHelper.createToken(sysUser.getId()+"", sysUser.getUsername());

//        当认证成功后，取出 认证成功后的用户的按钮权限信息
        Collection<GrantedAuthority> authorities = customerUser.getAuthorities();

//        将按钮权限信息存起来
//        redis
//        Redis 独立数据库存在 redis 性能高
//        数据类型的转换  对象转JSON   【 JSON.toJSONString】
        this.redisTemplate.opsForValue().set(sysUser.getUsername(), JSON.toJSONString(authorities));

        // 记录成功的日志

        sysLoginLogService.recordLoginLog(customerUser.getUsername(),1, IpUtil.getIpAddress(request),"登录成功");

        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put("token",token);

//        3、转为json 并且 要统一返回值result 给到前端【需要引入工具类 ResponseUtil】
        ResponseUtil.out(response, Result.ok(hashMap));
    }


    //    4、认证失败的逻辑
    //        4.1 给出提示语 认证失败
    //        4.2 给出状态码 407 前端根据状态码给出提示
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        if(failed.getCause() instanceof RuntimeException){
            // 是 系统的异常信息  null、空指针。。
            ResponseUtil.out(response, Result.build(null, 204, failed.getMessage()));
        }else{
            // 认证异常信息
            ResponseUtil.out(response,Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR));
        }
    }

}